package sunbt.gistools.util;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.*;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import sunbt.gistools.exception.BusinessException;
import sunbt.gistools.exception.enums.ErrorCodeEnum;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

/**
 * @author sunbt
 * @version 1.0
 * @date 2025/7/17 15:46
 */
public class ShapeFileToGeoJsonUtils {

    /**
     * 将Shapefile文件转换为GeoJSON格式的字符串，返回的坐标格式是4326的
     *
     * @param shapefilePath Shapefile文件的路径
     * @return 包含属性信息的GeoJSON字符串
     */
    public static String convertShapefileToGeoJSON(String shapefilePath) {
        FileDataStore store = null; // 用于存储Shapefile数据的对象
        String geoJson = null; // 用于存储最终生成的GeoJSON字符串


        String encoding = ShapeFileUtils.readCpgFileEncoding(shapefilePath);
        try {
            // 使用ShapefileDataStoreFactory创建数据存储对象，并设置字符编码
            File file = new File(shapefilePath);
            Map<String, Object> params = new HashMap<>();
            params.put("url", file.toURI().toURL());

            ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
            store = (ShapefileDataStore) dataStoreFactory.createDataStore(params);
            ((ShapefileDataStore) store).setCharset(Charset.forName(encoding)); // 设置字符编码为GBK

            SimpleFeatureCollection featureCollection = store.getFeatureSource().getFeatures();
            // 获取Shapefile中的所有要素，作为一个简单的要素集合


            // 第二步：设置目标坐标系，默认是EPSG:4326
            String epsgCode = "EPSG:4490";
            CoordinateReferenceSystem targetCRS = CRS.decode(epsgCode);

            // 获取Shapefile的原始坐标系
            CoordinateReferenceSystem sourceCRS = store.getSchema().getCoordinateReferenceSystem();

            // 如果原始坐标系和目标坐标系不同，则进行坐标转换
            DefaultFeatureCollection transformedFeatureCollection = new DefaultFeatureCollection(null, featureCollection.getSchema());
            if (!CRS.equalsIgnoreMetadata(sourceCRS, targetCRS)) {
                MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
                try (SimpleFeatureIterator iterator = featureCollection.features()) {
                    while (iterator.hasNext()) {
                        SimpleFeature feature = iterator.next();
                        Geometry geometry = (Geometry) feature.getDefaultGeometry();
                        Geometry transformedGeometry = org.geotools.geometry.jts.JTS.transform(geometry, transform);

                        // 调整坐标顺序为 [经度, 纬度]
//                        transformCoordinatesToLongLat(transformedGeometry);

                        SimpleFeature transformedFeature = SimpleFeatureBuilder.build(feature.getFeatureType(), feature.getAttributes(), feature.getID());
                        transformedFeature.setDefaultGeometry(transformedGeometry);
                        transformedFeatureCollection.add(transformedFeature);
                    }
                }
            } else {
                // 如果坐标系相同，不需要转换，直接使用原始要素集合并调整坐标顺序
                try (SimpleFeatureIterator iterator = featureCollection.features()) {
                    while (iterator.hasNext()) {
                        SimpleFeature feature = iterator.next();
                        Geometry geometry = (Geometry) feature.getDefaultGeometry();

                        // 调整坐标顺序为 [经度, 纬度]
//                        transformCoordinatesToLongLat(geometry);
                        SimpleFeature transformedFeature = SimpleFeatureBuilder.build(feature.getFeatureType(), feature.getAttributes(), feature.getID());
                        transformedFeature.setDefaultGeometry(geometry);
                        transformedFeatureCollection.add(transformedFeature);
                    }
                }
            }

            // 第三步：使用FeatureJSON将要素转换为GeoJSON格式
            FeatureJSON fjson = new FeatureJSON(); // 创建一个FeatureJSON对象，用于处理GeoJSON转换
            StringWriter writer = new StringWriter(); // 创建一个StringWriter，用于将GeoJSON数据写入字符串
            fjson.writeFeatureCollection(transformedFeatureCollection, writer);
            geoJson = writer.toString(); // 将StringWriter中的内容转换为字符串并存储

            // 第四步：使用FastJSON解析GeoJSON并移除crs字段
            JSONObject jsonObject = JSON.parseObject(geoJson);
            jsonObject.remove("crs"); // 移除可能存在的crs字段
            geoJson = jsonObject.toJSONString(); // 将修改后的JSON对象转换回字符串
        } catch (IOException | FactoryException | TransformException e) {
            // 捕获异常，打印错误堆栈信息
            e.printStackTrace();
        } finally {
            // 第五步：资源释放
            if (store != null) {
                store.dispose(); // 释放Shapefile数据存储对象，以避免资源泄漏
            }
        }

        return geoJson; // 返回最终生成的GeoJSON字符串
    }

    /**
     * 将Shapefile文件转换为GeoJSON格式，并移除Z坐标
     * 此方法首先读取Shapefile文件的编码，然后使用GeoTools库将Shapefile数据转换为GeoJSON格式
     *
     * @param shapefilePath Shapefile文件的路径，包括文件名和扩展名
     * @return 返回转换后的GeoJSON字符串，如果不成功则返回null
     */
    public static String convertShapefileToGeoJSONRemoveZ(String shapefilePath) {
        FileDataStore store = null;
        String geoJson = null;

        // 读取 Shapefile 文件的编码
        String encoding = ShapeFileUtils.readCpgFileEncoding(shapefilePath);

        try {
            // 使用 FileDataStoreFinder 创建数据存储对象
            File file = new File(shapefilePath);
            store = FileDataStoreFinder.getDataStore(file);

            // 设置数据存储的字符编码
            if (store instanceof org.geotools.data.shapefile.ShapefileDataStore) {
                ((org.geotools.data.shapefile.ShapefileDataStore) store).setCharset(Charset.forName(encoding));
            }

            // 获取特征集合
            SimpleFeatureSource featureSource = store.getFeatureSource();
            SimpleFeatureCollection featureCollection = featureSource.getFeatures();

            // 构建 GeoJSON 对象
            JSONObject geoJsonObj = new JSONObject();
            geoJsonObj.put("type", "FeatureCollection");

            JSONArray featuresArray = new JSONArray();

            // 使用迭代器遍历 SimpleFeatureCollection
            try (SimpleFeatureIterator iterator = featureCollection.features()) {
                while (iterator.hasNext()) {
                    SimpleFeature feature = iterator.next();
                    JSONObject featureObj = new JSONObject();
                    featureObj.put("type", "Feature");

                    // 处理几何信息
                    JSONObject geometryObj = new JSONObject();
                    Object geometry = feature.getDefaultGeometry();


                    if (geometry != null) {

                        Coordinate firstCoordinate = getFirstCoordinate(geometry);
                        checkCoordinateIsLatLon(firstCoordinate);

                        String geometryType = geometry.getClass().getSimpleName();
                        geometryObj.put("type", geometryType);
                        if ("Polygon".equals(geometryType)) {
                            Polygon polygon = (Polygon) geometry;
                            JSONArray polygonCoordinatesArray = processPolygon(polygon);
                            geometryObj.put("coordinates", polygonCoordinatesArray);
                        } else if ("MultiPolygon".equals(geometryType)) {
                            MultiPolygon multiPolygon = (MultiPolygon) geometry;
                            JSONArray multiPolygonCoordinatesArray = processMultiPolygon(multiPolygon);
                            geometryObj.put("coordinates", multiPolygonCoordinatesArray);
                        }
                    }
                    featureObj.put("geometry", geometryObj);

                    // 处理属性信息
                    JSONObject propertiesObj = new JSONObject();
                    SimpleFeatureType featureType = feature.getType();
                    for (AttributeDescriptor descriptor : featureType.getAttributeDescriptors()) {
                        String attributeName = descriptor.getLocalName();
                        if (!attributeName.equals(featureType.getGeometryDescriptor().getLocalName())) {
                            Object attributeValue = feature.getAttribute(attributeName);
                            propertiesObj.put(attributeName, attributeValue);
                        }
                    }
                    featureObj.put("properties", propertiesObj);

                    featuresArray.add(featureObj);
                }
            }
            // 确保迭代器被关闭，释放资源

            geoJsonObj.put("features", featuresArray);
            geoJson = geoJsonObj.toJSONString();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 资源释放
            if (store != null) {
                store.dispose();
            }
        }

        return geoJson;
    }




    /**
     * 获取几何对象的第一个点坐标。
     *
     * @param geometry 几何对象
     * @return 第一个点的坐标
     */
    private static Coordinate getFirstCoordinate(Object geometry) {
        if (geometry instanceof Point) {
            return ((Point) geometry).getCoordinate();
        } else if (geometry instanceof Polygon) {
            return ((Polygon) geometry).getExteriorRing().getCoordinateN(0);
        } else if (geometry instanceof MultiPolygon) {
            MultiPolygon multiPolygon = (MultiPolygon) geometry;
            Polygon firstPolygon = (Polygon) multiPolygon.getGeometryN(0);
            return firstPolygon.getExteriorRing().getCoordinateN(0);
        }
        return null;
    }

    /**
     * 检查坐标是否为经纬度。
     *
     * @param coordinate 要检查的坐标
     * @throws IllegalArgumentException 如果坐标不是经纬度
     */
    private static void checkCoordinateIsLatLon(Coordinate coordinate) {
        if (coordinate != null) {
            double x = coordinate.x;
            double y = coordinate.y;
            if (x < -180 || x > 180 || y < -90 || y > 90) {
                throw new BusinessException(ErrorCodeEnum.BUSINESS_ERROR.getCode(), "坐标系不正确，请确保坐标系为国家2000的4490坐标系！");
            }
        }
    }

    /**
     * 处理 Polygon 类型的几何对象，将其坐标信息转换为 JSON 数组。
     *
     * @param polygon 要处理的 Polygon 对象
     * @return 包含多边形坐标信息的 JSON 数组
     */
    private static JSONArray processPolygon(Polygon polygon) {
        JSONArray polygonCoordinatesArray = new JSONArray();
        processLinearRing(polygon.getExteriorRing(), polygonCoordinatesArray);
        for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
            processLinearRing(polygon.getInteriorRingN(i), polygonCoordinatesArray);
        }
        return polygonCoordinatesArray;
    }

    /**
     * 处理 LineString 类型的环，将其坐标信息添加到指定的 JSON 数组中。
     *
     * @param ring                    要处理的 LineString 环
     * @param polygonCoordinatesArray 用于存储环坐标信息的 JSON 数组
     */
    private static void processLinearRing(LineString ring, JSONArray polygonCoordinatesArray) {
        JSONArray ringCoordinatesArray = new JSONArray();
        for (Coordinate coord : ring.getCoordinates()) {
            JSONArray pointCoordinatesArray = new JSONArray();
            pointCoordinatesArray.add(coord.x);
            pointCoordinatesArray.add(coord.y);
            ringCoordinatesArray.add(pointCoordinatesArray);
        }
        polygonCoordinatesArray.add(ringCoordinatesArray);
    }

    /**
     * 处理 MultiPolygon 类型的几何对象，将其坐标信息转换为 JSON 数组。
     *
     * @param multiPolygon 要处理的 MultiPolygon 对象
     * @return 包含多个多边形坐标信息的 JSON 数组
     */
    private static JSONArray processMultiPolygon(MultiPolygon multiPolygon) {
        JSONArray multiPolygonCoordinatesArray = new JSONArray();
        for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
            Polygon polygon = (Polygon) multiPolygon.getGeometryN(i);
            JSONArray polygonCoordinatesArray = processPolygon(polygon);
            multiPolygonCoordinatesArray.add(polygonCoordinatesArray);
        }
        return multiPolygonCoordinatesArray;
    }

}
